本地缓存的原理及技术选型参考 | 文末送书

创建时间:2018/4/30 0:12
标签:微信
来源:http://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650121227&idx=1&sn=8c6365c40176fa8055012a16bc9c680e&chksm=f36bb92ac41c303c6e2f117a9b55b7f5722da13610ac629d6f3e1446cc3b3d55f611f220be89&mpshare=1&scene=1&srcid=0430Yj7GBw0j6z2rTNW9uX9t#rd

本地缓存的原理及技术选型参考 | 文末送书

原创2018-04-29林湾村龙猫Hollis

互联网架构设计的五大要素:高性能、高可用、可伸缩性、可扩展性、安全。


如何做到高性能、高可用,缓存是一大助力。


我们知道,绝大部分的时候,读数据写数据符合二八定律。并且读数据中,百分之二十是数据被经常读取(热数据)。那么我们解决这百分之二十的数据的方法就可以取得很好的一个性能。

缓存分类

从很多互联网架构设计中可以看到,从用户在浏览器上输入网址开始,经历了太多的缓存。我大概列举一下: 


1. 输入网址后,查询浏览器缓存 

2. 查询浏览器dns缓存 

3. 查询操作系统dns缓存 

4. 请求dns服务器,查询dns服务器缓存 

5. 获得ip,静态资源走cdn缓存。动态数据走服务器

6. 如果配置了页面缓存,走页面缓存 

7. 如果配置了本地缓存(localcache),走本地缓存 

8. 如果配置了分布式缓存(如redis等等),走分布式缓存 

9. 数据库操作,数据库缓存

这里,我想主要讲讲2种localcache的选择,为什么要引入localcache,可以用《互联网架构的三板斧》中的一句话来说明:

数据访问热点,比如Detail中对某些热点商品的访问度非常高,即使是Tair缓存这种Cache本身也有瓶颈问题,一旦请求量达到单机极限也会存在热点保护问题。


有时看起来好像很容易解决,比如说做好限流就行,但你想想一旦某个热点触发了一台机器的限流阀值,那么这台机器Cache的数据都将无效,进而间接导致Cache被击穿,请求落地应用层数据库出现雪崩现象。


这类问题需要与具体Cache产品结合才能有比较好的解决方案,这里提供一个通用的解决思路,就是在Cache的client端做本地Localcache,当发现热点数据时直接Cache在client里,而不要请求到Cache的Server。

还有一个原因是,在做秒杀的时候,我可以在每一台应用服务器中设置一个有失效时间的商品剩余数量的计数器,以达到尽可能在调用链前面拦截非有效请求。

分布式缓存

如何部署分布式缓存,这里不细说。列一下常见的部署方式: 这种方式是不太好扩机器。有一种比较好的方式是:一致性哈希算法。 可以参考这篇文章:《一致性哈希算法》

Localcache

我对比了两种LocalCache。google的Guava库中的cache模块 和Ehcache。

Guava中本地缓存基本原理为:ConcurrentMap(利用分段锁降低锁粒度) + LRU算法。

对应map中的每一个键值对,可以设置过期时间,也可以通过如LRU(Least Recently Used 最近最少使用)做淘汰策略。

LRU算法实现原理本质上是一个hashmap+双向链表,每次访问操作都将节点放在链表头部,尾部自然就是最旧的节点了。 


Ehcache缓存是一个比较重的localcache。有一个比较有意思的点是:支持磁盘缓存,这样妈妈再也不用担心内存不够用了。

我的需求是:可以不用担心内存不足的问题,做一些配置或不需要强一致性数据缓存。甚至可以做伪热加载配置。

因此,我选择使用了Ehcache。如果只是做内存缓存,建议使用guava,有很多有意思的东西,比如缓存失效,自动从数据源加载数据等等。

Ehcache工具类

根据自身需要,封装了一个只走磁盘的无容量限制的localcache工具类,仅供参考,考虑到可以放在多个地方,因此没有走配置文件 


java package com.fenqile.creditcard.appgatewaysale.provider.util;

import com.alibaba.fastjson.JSONObject; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import net.sf.ehcache.config.CacheConfiguration; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory;

import java.security.MessageDigest;

/** 
* User: Rudy Tan 
* Date: 2018/3/28 
* * 本地缓存工具,基于ehcache,磁盘存储 
* * 可以运用于配置文件、接口数据等等, 
**/
 
public class LocalCacheUtil {

private static final CacheManager cacheManager = CacheManager.create();

private static Logger LOG = LoggerFactory.getLogger(LocalCacheUtil.class);

private static String md5(String str) {
   try {
       MessageDigest md = MessageDigest.getInstance("MD5");
       byte[] bytes = md.digest(str.getBytes("utf-8"));

       final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
       StringBuilder ret = new StringBuilder(bytes.length * 2);
       for (int i=0; i<bytes.length; i++) {
           ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
           ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
       }
       return ret.toString();
   }
   catch (Exception e) {
       throw new RuntimeException(e);
   }
}

private static Cache getCacheInstance(){
   String cacheKey = "local_cache_"+ md5(Thread.currentThread().getStackTrace()[1].getClassName());
   if (!cacheManager.cacheExists(cacheKey)){
       synchronized (cacheManager){
           if (!cacheManager.cacheExists(cacheKey)){
               CacheConfiguration cacheConfiguration =  new CacheConfiguration();
               cacheConfiguration.setTimeToIdleSeconds(60);
               cacheConfiguration.setTimeToLiveSeconds(60);
               cacheConfiguration.setName(cacheKey);
               cacheConfiguration.setMaxEntriesLocalHeap(1);
               cacheConfiguration.setMaxEntriesLocalDisk(100000);
               cacheConfiguration.setEternal(false);
               cacheConfiguration.setOverflowToDisk(true);
               cacheConfiguration.setMaxElementsInMemory(1);
               cacheConfiguration.setCopyOnRead(true);
               cacheConfiguration.setCopyOnWrite(true);

               Cache cache = new Cache(cacheConfiguration);
               cacheManager.addCache(cache);
           }
       }
   }
   return cacheManager.getCache(cacheKey);
}

private static Element serialization(String key, Object value, Integer expireTime){
   if (StringUtils.isEmpty(key)
           || null == expireTime
           || 0 == expireTime){
       return null;
   }

   String clazz = "";
   String content = "";
   if (null == value){
       clazz = "null";
       content = clazz + "_class&data_null";
   }else {
       clazz = value.getClass().getName();
       content = clazz + "_class&data_"+ JSONObject.toJSONString(value);
   }

   return new Element(key, content, expireTime, expireTime);
}

private static Object unSerialization(Element element){
   if (null == element){ return null; }

   String content = (String) element.getObjectValue();
   String[] data = content.split("_class&data_");
   Object response = null;
   try {
       if ("null".equalsIgnoreCase(data[0])){
           return null;
       }
       response = JSONObject.parseObject(data[1], Class.forName(data[0]));
   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   }

   return response;
}


/**
* 设置本地缓存
*/

public static boolean setCache(String key, Object value, Integer expireTime){
   Cache cache = getCacheInstance();
   if (null == cache){
       LOG.info("setCache:cache is null, {}, {}, {}", key, value, expireTime);
       return false;
   }

   if (StringUtils.isEmpty(key)
           || null == expireTime
           || 0 == expireTime){
       LOG.info("setCache:params is not ok, {}, {}, {}", key, value, expireTime);
       return false;
   }

   synchronized (cache){
       cache.put(serialization(key, value, expireTime));
       cache.flush();
   }
   return true;
}

/**
* 获取本地缓存
*/

public static Object getCache(String key){
   Cache cache = getCacheInstance();
   if (null == cache
           || StringUtils.isEmpty(key)){
       LOG.info("getCache:params is not ok, {}", key);
       return null;
   }

   Element element = cache.get(key);
   return unSerialization(element);
}

/**
* 清理本地缓存
*/

public static boolean delCache(String key){
   Cache cache = getCacheInstance();
   if (null == cache){
       LOG.info("delCache:cache is null, {}", key);
       return true;
   }

   if (StringUtils.isEmpty(key)){
       LOG.info("delCache:params is not ok, {}", key);
       return true;
   }

   synchronized (cache){
       cache.put(serialization(key, null0));
       cache.flush();
   }
   return true;
}
}


声明:本文来自粉丝投稿,Hollis已获得独家授权,原作者:林湾村龙猫


福利时间


为答谢粉丝们长期以来的厚爱,四月份第三次送书,本次送书 2 本。


书籍介绍如下:


疯狂Spring Cloud微服务架构实战


本书以SpringCloud为基础,深入讲解微服务开发的相关框架,包括服务管理框架Eureka、负载均衡框架Ribbon、服务客户端Feign、容错框架Hystrix、消息框架Stream等。除了介绍这些微服务相关的框架外,在本书的第11章,还介绍了如何使用Spring Data框架操作各个主流数据库(MySQLMongoDBRedis)。在第12章,以一个案例为基础结束本书内容,在该章中讲解了模板引擎Thymeleaf,整本书将会为大家提供一整套微服务应用开发的解决方案。

 

适读人群:本书适合有一定Java开发基础的技术人员,尤其是正在使用或准备使用微服务构建高并发、大数据应用的技术人员及团队。


购买链接


参与方式:

2本书均通过抽奖送出,几乎无门槛。1本送给星球球友,1本送给公众号粉丝。活动截止时间 5月1日 10:00。


1、知识星球球友。进入<Hollis和他的朋友们>知识星球,置顶帖中抽奖二维码扫码参与。


PS:活动截止之前加入我的知识星球均可参与抽奖。现在加入即可享受知识问答、面试指导、付费文章分享、海量资料分享、送书福利等。快来和240+朋友共同进步吧。


2、公众号粉丝。点击抽奖链接进入抽奖页面参与抽奖。


PS:Hollis的送书活动几乎都是无门槛的,如果你觉得Hollis还可以,可以推荐给你身边的朋友。近期还会有多场送书活动。


- MORE | 更多精彩文章 -

如果你看到了这里,说明你喜欢本文。

那么请长按二维码,关注Hollis

转发朋友圈,是对我最大的支持。

    阅读原文